using System;
using Unity.Collections;
using Reinterpret.Net;

namespace Gltf
{

    /**
     * @brief Indicates the status of an accessor view.
     *
     * The {@link AccessorView} constructor always completes successfully. However,
     * it may not always reflect the actual content of the {@link Accessor}, but
     * instead indicate that its {@link AccessorView::size} is 0. This enumeration
     * provides the reason.
     */
    enum AccessorViewStatus
    {
        /**
         * @brief This accessor is valid and ready to use.
         */
        Valid,

        /**
         * @brief The accessor index does not refer to a valid accessor.
         */
        InvalidAccessorIndex,

        /**
         * @brief The accessor's bufferView index does not refer to a valid
         * bufferView.
         */
        InvalidBufferViewIndex,

        /**
         * @brief The accessor's bufferView's buffer index does not refer to a valid
         * buffer.
         */
        InvalidBufferIndex,

        /**
         * @brief The accessor is too large to fit in its bufferView.
         */
        BufferViewTooSmall,

        /**
         * @brief The accessor's bufferView is too large to fit in its buffer.
         */
        BufferTooSmall,

        /**
         * @brief The `sizeof(T)` does not match the accessor's
         * {@link Accessor::computeBytesPerVertex}.
         */
        WrongSizeT,

        /**
         * @brief The {@link Accessor:type} is invalid.
         */
        InvalidType,

        /**
         * @brief The {@link Accessor::componentType} is invalid.
         */
        InvalidComponentType,
    };

    /**
     * @brief A view on the data of one accessor of a glTF asset.
     *
     * It provides the actual accessor data like an array of elements.
     * The type of the accessor elements is determined by the template
     * parameter. Instances are usually from an {@link Accessor},
     * and the {@link operator[]()} can be used to access the elements:
     *
     * @snippet TestAccessorView.cpp createFromAccessorAndRead
     *
     * @tparam T The type of the elements in the accessor.
     */
    unsafe class AccessorView<T> where T : unmanaged
    {

        public byte[] _pData;
        private int _stride;
        private int _offset;
        private int _size;
        private AccessorViewStatus _status;

        /**
         * @brief The type of the elements in the accessor.
         */
        T value_type;

        /**
         * @brief Construct a new instance not pointing to any data.
         *
         * The new instance will have a {@link size} of 0 and a {@link status} of
         * `AccessorViewStatus::InvalidAccessorIndex`.
         *
         * @param status The status of the new accessor. Defaults to
         * {@link AccessorViewStatus::InvalidAccessorIndex}.
         */
        public unsafe AccessorView(AccessorViewStatus status = AccessorViewStatus.InvalidAccessorIndex)
        {
            _pData = new byte[0]; ;
            _stride = 0;
            _offset = 0;
            _size = 0;
            _status = status;
        }

        /**
         * @brief Creates a new instance from low-level parameters.
         *
         * The provided parameters are not validated in any way, and so this overload
         * can easily be used to access invalid memory.
         *
         * @param pData The raw data buffer from which to read.
         * @param stride The stride, in bytes, between successive elements.
         * @param offset The offset from the start of the buffer to the first element.
         * @param size The total number of elements.
         */
        public unsafe AccessorView(byte[] pData, Int64 stride, Int64 offset, Int64 size)
        {
            _pData = pData;
            _stride = (int)stride;
            _offset = (int)offset;
            _size = (int)size;
            _status = AccessorViewStatus.Valid;
        }

        /**
         * @brief Creates a new instance from a given model and {@link Accessor}.
         *
         * If the accessor cannot be viewed, the construct will still complete
         * successfully without throwing an exception. However, {@link size} will
         * return 0 and
         * {@link status} will indicate what went wrong.
         *
         * @param model The model to access.
         * @param accessor The accessor to view.
         */
        public AccessorView(Model model, Accessor accessor) : this(AccessorViewStatus.InvalidAccessorIndex)
        {
            this.create(model, accessor);
        }

        /**
         * @brief Creates a new instance from a given model and accessor index.
         *
         * If the accessor cannot be viewed, the construct will still complete
         * successfully without throwing an exception. However, {@link size} will
         * return 0 and
         * {@link status} will indicate what went wrong.
         *
         * @param model The model to access.
         * @param accessorIndex The index of the accessor to view in the model's
         * {@link Model::accessors} list.
         */
        public AccessorView(Model model, int accessorIndex) : this(AccessorViewStatus.InvalidAccessorIndex)
        {
            Accessor pAccessor = Model.getSafe(model.accessors, accessorIndex);
            if (!pAccessor)
            {
                this._status = AccessorViewStatus.InvalidAccessorIndex;
                return;
            }

            this.create(model, pAccessor);
        }

        /**
         * @brief Provides the specified accessor element.
         *
         * @param i The index of the element.
         * @returns The constant reference to the accessor element.
         * @throws A `std::range_error` if the given index is negative
         * or not smaller than the {@link size} of this accessor.
         */
        public T this[Int64 i]
        {
            get
            {
                if (i < 0 || i >= this._size)
                {
                    throw new System.Exception("index out of range");
                }

                return _pData.Reinterpret<T>((int)(i * this._stride + this._offset));
            }
            set
            {
                value.Reinterpret(new Span<byte>(_pData), (int)(i * this._stride + this._offset));
            }
        }

        /**
         * @brief Returns the size (number of elements) of this accessor.
         *
         * This is the number of elements of type `T` that this accessor contains.
         *
         * @returns The size.
         */
        public int size() { return this._size; }

        /**
         * @brief Gets the status of this accessor view.
         *
         * Indicates whether the view accurately reflects the accessor's data, or
         * whether an error occurred.
         */
        public AccessorViewStatus status() { return this._status; }

        private void create(Model model, Accessor accessor)
        {
            BufferView pBufferView = Model.getSafe(model.bufferViews, accessor.bufferView);
            if (!pBufferView)
            {
                this._status = AccessorViewStatus.InvalidBufferViewIndex;
                return;
            }

            Gltf.Buffer pBuffer = Model.getSafe(model.buffers, pBufferView.buffer);
            if (!pBuffer)
            {
                this._status = AccessorViewStatus.InvalidBufferIndex;
                return;
            }

            byte[] data = pBuffer.cesium.data;
            Int64 bufferBytes = (Int64)(data.Length);
            if (pBufferView.byteOffset + pBufferView.byteLength > bufferBytes)
            {
                this._status = AccessorViewStatus.BufferTooSmall;
                return;
            }

            Int64 accessorByteStride = accessor.computeByteStride(model);
            Int64 accessorComponentElements = accessor.computeNumberOfComponents();
            Int64 accessorComponentBytes = accessor.computeByteSizeOfComponent();
            Int64 accessorBytesPerStride = accessorComponentElements * accessorComponentBytes;

            if (sizeof(T) != accessorBytesPerStride)
            {
                this._status = AccessorViewStatus.WrongSizeT;
                return;
            }

            Int64 accessorBytes = accessorByteStride * accessor.count;
            Int64 bytesRemainingInBufferView = pBufferView.byteLength -
                (accessor.byteOffset + accessorByteStride * (accessor.count - 1) +
                 accessorBytesPerStride);
            if (accessorBytes > pBufferView.byteLength || bytesRemainingInBufferView < 0)
            {
                this._status = AccessorViewStatus.BufferViewTooSmall;
                return;
            }

            this._pData = pBuffer.cesium.data;
            this._stride = (int)accessorByteStride;
            this._offset = (int)(accessor.byteOffset + pBufferView.byteOffset);
            this._size = (int)accessor.count;
            this._status = AccessorViewStatus.Valid;
        }
    }
}